package xj.model.uml;

import static xj.model.uml.UMLModelConstants.ATTR_DIAGRAM_TYPE;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import xj.meta.ModelElement;

public abstract class UMLDiagram 
  extends NamedElement {

  public enum DiagramType {
      Activity_Diagram, 
      Class_Diagram, 
      Communication_Diagram, 
      Component_Diagram, 
      Composite_Structure_Diagram, 
      Deployment_Diagram, 
      Interaction_Diagram, 
      Object_Diagram, 
      Package_Diagram, 
      Sequence_Diagram, 
      State_Machine_Diagram, 
      Timing_Diagram, 
      Use_Case_Diagram,
  }

  public void add(ModelElement parent, ModelElement e) {
    add(parent, e, null);
  }

  public void add(ModelElement parent, ModelElement e, Object arg) {
    if (e != null) {
      if (e instanceof Note) {
	BasicModelElement pmodel = null;
	if (parent instanceof BasicModelElement) {
	  pmodel = (BasicModelElement) parent;
	} else {
	  pmodel = this;
	}
	pmodel.addNote((Note) e);
      } else if (e instanceof UMLAssociation) {
	addUMLAssociation((UMLAssociation) e);
      }
    }
  }

  public void add(ModelElement e) {
    add(null, e);
  }

  public void handleNotesLink(UMLAssociation link) { 
    if (link != null &&
	link.getType() == UMLAssociation.AssociationType.Notes_Link) { 
      //System.out.println("UMLDiagram.handleNotesLink() @1"); 
      ModelElement right = link.getRightElement();
      ModelElement left = link.getLeftElement(); 
      Note note = null;
      BasicModelElement other = null; 
      if (left instanceof Note && 
	  right instanceof BasicModelElement) { 
	note = (Note) left;
	other = (BasicModelElement) right;
      } else if (right instanceof Note && 
		 left instanceof BasicModelElement) { 
	note = (Note) right;
	other = (BasicModelElement) left;
      } 
      if (note != null && other != null) { 
	BasicModelElement pmodel = (BasicModelElement) note.getOwner();
	if (pmodel != null) { 
	  pmodel.removeOwnedElement(note);
	}
	other.addOwnedElement(note);	
      }
    }
  }

  public void addUMLAssociation(UMLAssociation assoc) {
    if (assoc != null) {
      associations.add(assoc);
      addOwnedElement(assoc);
      if (assoc.getType() == UMLAssociation.AssociationType.Notes_Link) { 
	handleNotesLink(assoc); 
      }
    }
  }

  public void removeUMLAssociation(UMLAssociation assoc) {
    if (assoc != null) {
      associations.remove(assoc);
      removeOwnedElement(assoc);
    }
  }

  public Set<UMLAssociation> getUMLAssociations() {
    return associations;
  }

  @Override
  public String toString() {
    return "UMLDiagrm: name=" + name + " type=" + type;
  }

  @Override
  public Object clone() 
    throws CloneNotSupportedException {
    UMLDiagram d = (UMLDiagram) super.clone();
    d.setType(type);

    d.associations = new HashSet<UMLAssociation>();
    Iterator<UMLAssociation> iter3 = associations.iterator();
    while (iter3.hasNext()) {
      UMLAssociation a = iter3.next();
      d.addUMLAssociation((UMLAssociation) a.clone());
    }
    return d;
  }

  public DiagramType getType() {
    return type;
  }

  public void setType(DiagramType type) {
    this.type = type;
    setAttribute(ATTR_DIAGRAM_TYPE, type);
  }

  public boolean checkConnect(ModelElement edgeModel, 
			      ModelElement nodeModel, 
			      boolean source) { 
    boolean edgeok = checkAllowedEdge(edgeModel);
    boolean nodeok = checkAllowedNode(nodeModel);

    //System.out.println("UMLDiagram.checkConnect() " + source + " " + edgeok + " " + nodeok);

    if (nodeModel instanceof Note) { 
      if (edgeModel instanceof UMLAssociation) { 
	UMLAssociation a = (UMLAssociation) edgeModel;
	if (a.getType() == UMLAssociation.AssociationType.Notes_Link) { 
	  return true;
	}
      }
    } else { 
      return (edgeok && nodeok);
    }
    return false;
    /*
    if (!edgeok && !nodeok) { 
      return true;
    } else { 
      return (edgeok && nodeok);
    }
    */
  }

  public boolean checkAllowedEdge(ModelElement m) {
    if (m != null &&
	(m instanceof UMLAssociation || 
	 m instanceof ActivityFlow ||
	 m instanceof Transition ||
	 m instanceof LifeLine ||
	 m instanceof UMLMessage)) { 
      return true;
    }       
    return false; 
  }

  public boolean checkAllowedNode(ModelElement m) { 
    if (m != null && 
	(m instanceof ActivityNode ||
	 m instanceof Actor ||
	 m instanceof Classifier ||
	 m instanceof UMLClass ||
	 m instanceof DataType ||
	 m instanceof MessageNode ||
	 m instanceof State ||
	 m instanceof UMLInterface || 
	 m instanceof UMLObject || 
	 m instanceof UMLPackage ||
	 m instanceof UMLSystem ||
	 m instanceof UMLSystemBoundary ||
	 m instanceof UseCase ||
	 m instanceof SequenceNode ||
	 m instanceof Note)) {
      return true;
    }
    return false; 
  }

  public boolean checkAllowSubnode(ModelElement parentModel, 
				   ModelElement nodeModel) {
    return true; 
  }

  public boolean checkStructure() { 
    return true;
  }  

  @Override
  public void deconstruct() {
    super.deconstruct();
    associations.clear();
  }

  protected UMLDiagram() {
  }

  protected UMLDiagram(String name) {
    super(name);
  }

  protected UMLDiagram(String name, DiagramType type) {
    super(name);
    setType(type);
  }

  protected static List<ModelElement> getPathToDiagram(ModelElement e) {
    if (e != null) {
      List<ModelElement> path = new ArrayList<ModelElement>();
      ModelElement p = e.getOwner();
      while (p != null && !(p instanceof UMLDiagram)) {
	path.add(0, p);
	p = p.getOwner();
      }
      return path;
    }
    return null;
  }

  protected static ModelElement findLeastCommonAncestor(ModelElement e1,
							ModelElement e2) {
    if (e1 != null && e2 != null) {
      if (e1 == e2) {
	return e1;
      }

      List<ModelElement> l1 = getPathToDiagram(e1);
      List<ModelElement> l2 = getPathToDiagram(e2);
      if (l1 != null && l2 != null) {
	int len1 = l1.size();
	int len2 = l2.size();
	if (len1 > 0 && len2 > 0) {
	  ModelElement c = null;
	  int len = Math.min(len1, len2);
	  for (int i = 0; i < len; i++) {
	    ModelElement r1 = l1.get(i);
	    ModelElement r2 = l2.get(i);
	    if (r1 == r2) {
	      c = r1;
	    } else {
	      break;
	    }
	  }
	  return c;
	}
      }
    }
    return null;
  }

  protected DiagramType type;

  protected Set<UMLAssociation> associations = new HashSet<UMLAssociation>();

}
